#pragma once

#include "Socket.hpp"
#include <poll.h>
#include <iostream>
#include <memory>

using namespace NetWork;

const static int default_rfds_max_size = 1024;

class PollServer
{
public:
    PollServer(int port) : _port(port),
                             _listen_socket(new TcpSocket(port)), 
                             _is_running(false), 
                             _rfds_max_size(default_rfds_max_size)
    {}
    
    ~PollServer()
    {
        delete[] _rfds;
    }

    void init()
    {
        _listen_socket->BuildListenSocketMethod(_port, default_backlog);

        _rfds = new pollfd[_rfds_max_size];
        for(int i = 0; i < _rfds_max_size; i++)
        {
            _rfds[i].fd = -1;
            _rfds[i].events = 0;
            _rfds[i].revents = 0;
        }

        // at the beginning, only listen_socket is in _rfds
        _rfds[0].fd = _listen_socket->GetSockfd();
        _rfds[0].events = POLLIN;
    }

    void loop()
    {
        _is_running = true;
        while (_is_running)
        {
            int timeout = -1;  

            // select() will modify the fds, before call select, we can print triggered fd debug info
            PrintDebug();

            int ret = ::poll(_rfds, _rfds_max_size, timeout);

            if (ret == -1)
            {
                std::cout << "call select error" << std::endl;
            }
            else if (ret == 0)
            {
                // timeout, do something
                std::cout << "select timeout... [timeout = 3 seconds]" << std::endl;
            }
            else
            {
                // handle socket events and call accept function
                HandlerEvent();
                // std::cout << "select success! handle socket events  [remaining time = "
                //     << timeout.tv_sec << "." << timeout.tv_usec << " seconds]" << std::endl;
            }
        }
        _is_running = false;
    }

private:
    void HandlerEvent()  // use revents to check which socket is triggered
    {
        for (int i = 0; i < _rfds_max_size; i++)
        {
            if (_rfds[i].fd == -1)
            {
                continue;
            }

            // valid sockfd
            int sockfd = _rfds[i].fd;
            short revents = _rfds[i].revents;

            // check if sockfd is in read_fds
            if (revents & POLLIN)
            {
                // 1. read event is triggered (new link is available)
                if (sockfd == _listen_socket->GetSockfd())
                {
                    // new connection
                    std::cout << "new connection" << std::endl;
                    // client socket will not block, because listen_socket is ready to accept
                    std::string client_ip;
                    uint16_t client_port;
                    int client_fd = _listen_socket->AcceptSocketOrDie(&client_ip, &client_port);
                    if (client_fd >= 0)
                    {
                        printf("new connection from %s:%d, client_fd = %d\n", client_ip.c_str(), client_port, client_fd);
                    }
                    else
                    {
                        std::cout << "accept socket error!!!" << std::endl;
                        continue;
                    }

                    // add client socket to _rfds
                    int pos = 0;
                    for (; pos < _rfds_max_size; pos++)
                    {
                        if (_rfds[pos].fd == -1)
                        {
                            _rfds[pos].fd = client_fd;
                            _rfds[pos].events = POLLIN;
                            std::cout << "get a new link, fd = " << client_fd << std::endl;
                            break;
                        }
                    }
                    if (pos == _rfds_max_size)
                    {
                        std::cout << "ERROR: too many clients, please increase the size of _rfds" << std::endl;
                        // TODO: close client_socket
                        close(client_fd);
                    }
                }
            }
            else
            {
                // 2. data is available for reading
                std::string data;
                ssize_t n = recv(sockfd, &data[0], 1024, 0);
                if (n)
                {
                    // recv available data from client
                    std::cout << "recv data from client # " << data << std::endl;
                    std::string echo_data = "echo received data: [" + data + "]";
                    send(sockfd, echo_data.c_str(), echo_data.size(), 0);
                }
                else
                {
                    // client closed connection
                    std::cout << "client closed connection" << std::endl;
                    // do clear
                    close(sockfd);
                    _rfds[i].fd = -1;
                    _rfds[i].events = 0;
                    _rfds[i].revents = 0;
                }
            }
        }
    }

    void PrintDebug()
    {
        std::cout << "current select rfds list is : ";
        for (int i = 0; i < _rfds_max_size; i++)
        {
            if (_rfds[i].fd == -1)
                continue;
            else
                std::cout << _rfds[i].fd << " ";
        }
        std::cout << std::endl;
        sleep(1);
    }

private:
    std::shared_ptr<Socket> _listen_socket;
    int _port;
    bool _is_running;
    struct pollfd* _rfds;
    int _rfds_max_size;
};